/*  
 *  Copyright 2006 Paul Jack.
 *
 *  This file is part of the Dex compiler suite. 
 *  
 *  Dex is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation; either version 2 of the License, or (at your
 *  option) any later version.
 *  
 *  Dex is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 *  Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */
package dex.compiler.checker;


import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.SortedSet;
import java.util.TreeSet;

import dex.compiler.checker.CheckerConfig;
import dex.compiler.checker.DefinitionChecker;
import dex.compiler.model.definition.DelegateDefinitionWalker;
import dex.compiler.model.definition.Unit;
import dex.compiler.problem.Problem;
import dex.test.StoreReporter;
import dex.test.TemplateSuite;
import dex.test.StoreReporter.Pair;

import junit.framework.AssertionFailedError;
import junit.framework.TestCase;


/**
 * Framework for testing a set of problems.
 * 
 * <p>The semantic analysis packages define several "static problem" classes:
 * {@link dex.compiler.checker.type.TypeProblems}, 
 * {@link dex.compiler.checker.ExpressionProblems},
 * {@link dex.compiler.checker.StatementProblems}, and 
 * {@link dex.compiler.checker.DefinitionProblems}.
 * These classes list all of the potential problems that can occur 
 * during semantic analysis.
 * 
 * <p>A concrete <code>ProblemTester</code> looks for an invalid Dex source
 * code file that tests each problem in a given static problem class.  For 
 * instance, the {@link TestTypeProblems} concrete subclass looks for 
 * an invalid Dex source file for each problem defined in {@link
 * dex.compiler.checker.type.TypeProblems}.
 *
 * <p>A particular defined problem can have more than one invalid Dex
 * source associated with it.  The Dex source files include a number.
 * For instance, the {@link dex.compiler.checker.type.AMBIGUOUS_TYPE}
 * problem has multiple invalid source files that are tested:
 * 
 * <ol>
 * <li>AMBIGUOUS_TYPE_1</li>
 * <li>AMBIGUOUS_TYPE_2</li>
 * <li>AMBIGUOUS_TYPE_3</li>
 * <li>AMBIGUOUS_TYPE_4</li>
 * </ol>
 * 
 * <p>And so on.  The <code>ProblemTester</code> class loads each 
 * invalid source file, parses it, and runs the parsed file through
 * semantic analysis.  The test expects the semantic analysis to
 * fail, with exactly one problem reported for the invalid source.
 * That problem of course is the value of the static field with 
 * the same name as the source file.  Each of the above files should
 * cause one {@link dex.compiler.checker.type.AMBIGUOUS_TYPE} to
 * be reported, and <i>only</i> that problem should be reported.
 * 
 * <p>Also, each concrete <code>ProblemTester</code> includes a 
 * "coverage" test that will determine whether there is at least
 * one invalid source file for every field defined by the static
 * problem class.
 * 
 * <p>The locations of the invalid Dex sources is determined by the 
 * <code>dir</code> parameter of the <code>ProblemTester</code>
 * constructor.	 Concrete subclasses generally hardcode this value.
 * The subclasses all assume that the tests will be run from the root
 * of the project directory.
 * 
 * <p>Additionally, there is a set of source files that predefine
 * classes, records, functions and so on for the tests to use.
 * These predefined live in the <code>test/dex/common</code>
 * directory.  That directory contains valid Dex source files.
 * Every file in that directory is loaded for every test case
 * executed by a <code>ProblemTester</code>.
 */
public abstract class ProblemTester extends TemplateSuite {

	
	final private Class cls;
	
	/**
	 * Constructs a new <code>ProblemTester</code>.
	 * 
	 * @param cls  the class that defines static problem fields
	 *              to test
	 * @param dir  the directory to look for invalid source files
	 *              that cause the problems listed in cls
	 */
	public ProblemTester(Class cls, String dir) throws IOException {
		super(new File(dir), getProblemNames(cls));
		this.cls = cls;
	}


	private static SortedSet<String> getProblemNames(Class cls) {
		TreeSet<String> result = new TreeSet<String>();
		Field[] fields = cls.getDeclaredFields();
		for (Field f: fields) {
			int m = f.getModifiers();
			if (Modifier.isStatic(m) && Modifier.isFinal(m) && Modifier.isPublic(m)) {
				result.add(f.getName());
			}
		}
		return result;
	}


	public void doTest(Unit unit, String ignored) throws Exception {
		CheckerConfig config = new CheckerConfig();
		StoreReporter reporter = new StoreReporter();
		config.setReporter(reporter);
		config.setProgram(getProgram());
		String expectedFieldName = getTestName(unit.getName());
		Field expectedField = cls.getField(expectedFieldName);
		Problem expected = (Problem)expectedField.get(null);
		config.setUnit(unit);

		new DelegateDefinitionWalker(new DefinitionChecker(config)).walk(unit);
		//unit.accept(new DefinitionChecker(config));
		int size = reporter.getProblems().size();
		if (size == 0) {
			throw new AssertionFailedError("Expected failure but file passed semantic analysis.");
		} else if (size > 1) {
			for (Pair p: reporter.getProblems()) {
				System.out.println(unit.getName() + " -- " + p.message);
			}
			throw new AssertionFailedError("Test failed more than once " + reporter.getProblems());
		} else {
			Pair pair = reporter.getProblems().get(0);
			Problem problem = pair.problem;
			if (problem != expected) {
				System.out.println(unit.getName() + " -- " + pair.message);
			}
			TestCase.assertEquals(expected, problem);
		}
	}
}
